#include "stdafx.h"
#include "builtin_providers.h"

#if !NO_BUILTIN_PROVIDERS
#include "debug.h"
#include "helper.h"
#include "curl_wrapper.h"
#include "deelx.h"


static grabber::provider_factory<provider_lyricwiki> g_lyricwiki;
static grabber::provider_factory<provider_lyricsplugin> g_lyricsplugin;

FORCEINLINE void how_to_sleep(t_size p_items)
{
	// 'Cause We all don't wanto flood server
	const DWORD magic_sleep[] = {300, 700, 1200, 1800, 2500, 3300, 4200, 5200, 5500};
	const t_size magic_items[] = {0, 60, 140, 270, 390, 500, 620, 880, 1000, infinite};

	for (int i = 0; i < _countof(magic_items); i++)
	{
		if (magic_items[i] < p_items && p_items <= magic_items[i])
		{
			Sleep(magic_sleep[i]);
			break;
		}
	}
}

pfc::string_list_impl * provider_lyricwiki::lookup(unsigned p_index, metadb_handle_list_cref p_meta, threaded_process_status & p_status, abort_callback & p_abort)
{
	// Regular Expression Class
	CRegexpT<char> regexp;
	MatchResult match;

	// Buffer
	pfc::string8_fast buff;
	pfc::string_list_impl * str_list = new pfc::string_list_impl;

	try
	{
		// Init fetcher
		curl_wrapper_simple fetcher(&m_config_item);

		for (t_size i = 0; i < p_meta.get_count(); ++i)
		{
			if (p_abort.is_aborting())
				break;

			// Sleep
			how_to_sleep(i);
			// Clear buff
			buff.reset();

			const metadb_handle_ptr & p = p_meta.get_item(i);

			if (p.is_empty())
			{
				str_list->add_item("");
				continue;
			}

			// Set progress
			pfc::string8_fast path = file_path_canonical(p->get_path());

			// add subsong index?
			if (p->get_subsong_index() > 0)
			{
				path.add_string(" /index:");
				path.add_string(pfc::format_uint(p->get_subsong_index()));
			}

			p_status.set_item_path(path);
			p_status.set_progress(i + 1, p_meta.get_count());

			pfc::string8_fast artist, album, title;
			static_api_ptr_t<titleformat_compiler> compiler;
			service_ptr_t<titleformat_object> script;

			// Get ARTIST
			compiler->compile_safe(script, "$replace($caps2(%artist%),' ','_')");
			p->format_title(NULL, artist, script, NULL);
			// Get ALBUM
			compiler->compile_safe(script, "[%album%]");
			p->format_title(NULL, album, script, NULL);
			// Get TITLE
			compiler->compile_safe(script, "$replace($caps2(%title%),' ','_')");
			p->format_title(NULL, title, script, NULL);

			// Fetching from HTTP
			// Set HTTP Address
			pfc::string8_fast url("http://lyricwiki.org/");

			url += fetcher.quote(artist);
			url += ":";
			url += fetcher.quote(title);

			// Get it now
			try
			{
				fetcher.fetch(url, buff);
			}
			catch (pfc::exception & e)
			{
				console_error(e.what());
				str_list->add_item("");
				continue;
			}
			catch (...)
			{
				str_list->add_item("");
				continue;
			}

			struct t_ti_href
			{
				const char * pos_ti, * pos_href;
				t_size len_ti, len_href;
			};

			const char * regex_lyricbox = "<div\\s+class\\s*=\\s*'lyricbox'\\s*>\\s*(.+?)</div>";

			// codes below are  *REALLY UGLY* :(
			// Get first Heading
			regexp.Compile("class=\"firstHeading\">.+?:(.+?)</");
			match = regexp.Match(buff.get_ptr());

			// should not fail
			if (!match.IsMatched())
			{
				str_list->add_item("");
				continue;
			}

			pfc::list_t<t_ti_href> sub_list;

			// Check toc title to find child
			if (buff.find_first("id=\"toctitle\">") != infinite)
			{
				pfc::string8 lyric;

				// Extract all subs and href
				regexp.Compile("class=\"toclevel-1\"><a\\s+href=\"#(.+?)\"><.+?class=\"toctext\">(\\w+\\..+?)</span>");
				match = regexp.Match(buff.get_ptr());

				if (match.IsMatched())
				{
					while (match.IsMatched())
					{
						t_ti_href title_addr;

						title_addr.pos_href = buff.get_ptr() + match.GetGroupStart(1);
						title_addr.pos_ti = buff.get_ptr() + match.GetGroupStart(2);
						title_addr.len_href = match.GetGroupEnd(1) - match.GetGroupStart(1);
						title_addr.len_ti = match.GetGroupEnd(2) - match.GetGroupStart(2);
						sub_list.add_item(title_addr);

						match = regexp.Match(buff.get_ptr(), match.GetEnd());
					}
				}
				else
				{
					// expression for extract lyrics
					regexp.Compile(regex_lyricbox, SINGLELINE);

					// match
					MatchResult result = regexp.Match(buff.get_ptr());

					// Get Group
					if (result.IsMatched())
					{
						int nStart = result.GetGroupStart(1);
						int nEnd = result.GetGroupEnd(1);

						pfc::string8 lyric(buff.get_ptr() + nStart, nEnd - nStart);
						convert_html_to_plain(lyric);
						str_list->add_item(lyric.get_ptr());
					}

					continue;
				}

				regexp.Compile(regex_lyricbox, SINGLELINE);

				// enumerate all titles
				for (t_size i = 0; i < sub_list.get_count(); ++i)
				{
					lyric.add_string(sub_list[i].pos_ti, sub_list[i].len_ti);
					lyric.add_string("<br/><br/>");

					t_size start, length;

					pfc::string8_fast to_find;

					to_find.set_string("name=\"");
					to_find.add_string(sub_list[i].pos_href, sub_list[i].len_href);
					to_find.add_string("\"");
					start = buff.find_last(to_find);

					if (i == sub_list.get_count() - 1)
					{
						length = buff.length();
					}
					else
					{
						to_find.set_string("name=\"");
						to_find.add_string(sub_list[i + 1].pos_href, sub_list[i + 1].len_href);
						to_find.add_string("\"");

						length = buff.length() - buff.find_last(to_find.get_ptr());
					}

					match = regexp.Match(buff.get_ptr(), length, start);

					if (match.IsMatched())
					{
						lyric.add_string(buff.get_ptr() + match.GetGroupStart(1), 
							match.GetGroupEnd(1) - match.GetGroupStart(1));
						lyric.add_string("<br/><br/>");
					}
					else
					{
						lyric.add_string("[This movement is an instrumental]<br/><br/>");
					}
				}

				convert_html_to_plain(lyric);
				str_list->add_item(lyric.get_ptr());
				continue;
			}

			// expression for extract lyrics
			regexp.Compile(regex_lyricbox, SINGLELINE);

			// match
			MatchResult result = regexp.Match(buff.get_ptr());

			// Get Group
			if (result.IsMatched())
			{
				int nStart = result.GetGroupStart(1);
				int nEnd = result.GetGroupEnd(1);
				pfc::string8 lyric(buff.get_ptr() + nStart, nEnd - nStart);

				convert_html_to_plain(lyric);
				str_list->add_item(lyric.get_ptr());
				continue;
			}

			str_list->add_item("");
		}
	}
	catch (pfc::exception & e)
	{
		console_error(e.what());
		delete str_list;
		return NULL;
	}
	catch (...)
	{
		delete str_list;
		return NULL;
	}
	
	return str_list;
}

pfc::string_list_impl * provider_lyricsplugin::lookup(unsigned p_index, metadb_handle_list_cref p_meta, threaded_process_status & p_status, abort_callback & p_abort)
{
	// Regular Expression Class
	CRegexpT<char> regexp;
	MatchResult match;

	// Buffer
	pfc::string8 buff;
	pfc::string_list_impl * str_list = new pfc::string_list_impl;

	try
	{
		// Init fetcher
		curl_wrapper_simple fetcher(&m_config_item);

		for (t_size i = 0; i < p_meta.get_count(); ++i)
		{
			if (p_abort.is_aborting())
				break;

			// Sleep
			how_to_sleep(i);
			// Clear buff
			buff.reset();

			const metadb_handle_ptr & p = p_meta.get_item(i);

			if (p.is_empty())
			{
				str_list->add_item("");
				continue;
			}

			// Set progress
			pfc::string8_fast path = file_path_canonical(p->get_path());

			// add subsong index?
			if (p->get_subsong_index() > 0)
			{
				path.add_string(" /index:");
				path.add_string(pfc::format_uint(p->get_subsong_index()));
			}

			p_status.set_item_path(path);
			p_status.set_progress(i + 1, p_meta.get_count());

			pfc::string8_fast artist, title;
			static_api_ptr_t<titleformat_compiler> compiler;
			service_ptr_t<titleformat_object> script;

			// Get ARTIST
			compiler->compile_safe(script, "[%artist%]");
			p->format_title(NULL, artist, script, NULL);
			// Get TITLE
			compiler->compile_safe(script, "[%title%]");
			p->format_title(NULL, title, script, NULL);

			// Fetching from HTTP
			// Set HTTP Address
			pfc::string8_fast url("http://www.lyricsplugin.com/winamp03/plugin/?artist=");

			url += fetcher.quote(artist);
			url += "&title=";
			url += fetcher.quote(title);

			// Get it now
			try
			{
				fetcher.fetch(url, buff);
			}
			catch (pfc::exception & e)
			{
				console_error(e.what());
				str_list->add_item("");
				continue;
			}
			catch (...)
			{
				str_list->add_item("");
				continue;
			}

			const char * regex_lyricbox = "<div\\s+id\\s*?=\\s*?\"lyrics\"\\s*?>[\\r\\n]*(.*?)[\\r\\n]*</div>";

			// expression for extract lyrics
			regexp.Compile(regex_lyricbox, SINGLELINE);

			// match
			MatchResult result = regexp.Match(buff.get_ptr());

			// Get Group
			if (result.IsMatched())
			{
				int nStart = result.GetGroupStart(1);
				int nEnd = result.GetGroupEnd(1);
				pfc::string8_fast lyric(buff.get_ptr() + nStart, nEnd - nStart);

				convert_html_to_plain(lyric);

				if (string_trim(lyric).get_length() > 0)
				{
					str_list->add_item(lyric);
					continue;
				}
			}

			str_list->add_item("");
		}
	}
	catch (pfc::exception & e)
	{
		console_error(e.what());
		delete str_list;
		return NULL;
	}
	catch (...)
	{
		delete str_list;
		return NULL;
	}

	return str_list;
}

#endif
